/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package util;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 *
 * @author JemiZhuu1984
 */
public class DdsFileReader {

	private static final int DDPF_FOURCC = 0x0004;
	private static final int DDSCAPS_TEXTURE = 0x1000;

	protected static class Color {

		private int r, g, b;

		public Color() {
			this.r = this.g = this.b = 0;
		}

		public Color(int r, int g, int b) {
			this.r = r;
			this.g = g;
			this.b = b;
		}

		public boolean equals(Object o) {
			if (this == o) {
				return true;
			}
			if (o == null || getClass() != o.getClass()) {
				return false;
			}

			final Color color = (Color) o;

			if (b != color.b) {
				return false;
			}
			if (g != color.g) {
				return false;
			}
			//noinspection RedundantIfStatement
			if (r != color.r) {
				return false;
			}

			return true;
		}

		public int hashCode() {
			int result;
			result = r;
			result = 29 * result + g;
			result = 29 * result + b;
			return result;
		}
	}

	protected static Dimension readHeaderDxt3(ByteBuffer buffer) {
		buffer.rewind();

		byte[] magic = new byte[4];
		buffer.get(magic);
		assert new String(magic).equals("DDS ");

		int version = buffer.getInt();
		assert version == 124;

		int flags = buffer.getInt();
		int height = buffer.getInt();
		int width = buffer.getInt();
		int pixels = buffer.getInt(); // ???
		int depth = buffer.getInt();
		int mipmaps = buffer.getInt();

		buffer.position(buffer.position() + 44); // 11 unused double-words

		int pixelFormatSize = buffer.getInt(); // ???
		int fourCC = buffer.getInt();
		assert fourCC == DDPF_FOURCC;

		byte[] format = new byte[4];
		buffer.get(format);
		assert new String(format).equals("DXT3");

		int bpp = buffer.getInt(); // bits per pixel for RGB (non-compressed) formats
		buffer.getInt(); // rgb bit masks for RGB formats
		buffer.getInt(); // rgb bit masks for RGB formats
		buffer.getInt(); // rgb bit masks for RGB formats
		buffer.getInt(); // alpha mask for RGB formats

		int unknown = buffer.getInt();
		assert unknown == DDSCAPS_TEXTURE;
		int ddsCaps = buffer.getInt(); // ???
		buffer.position(buffer.position() + 12);

		return new Dimension(width, height);
	}

	public static BufferedImage readDxt3(ByteBuffer buffer, String pixelFormat) throws UnknowPixelFormatException {
		buffer.order(ByteOrder.LITTLE_ENDIAN);
		Dimension dimension = readHeaderDxt3(buffer);

		return readDxt3Buffer(buffer, dimension.width, dimension.height, pixelFormat);
	}

	public static BufferedImage readDxt3Buffer(ByteBuffer buffer, int width, int height, String pixelFormat)
			throws UnknowPixelFormatException {
		buffer.order(ByteOrder.LITTLE_ENDIAN);

		int[] pixels = null;
		int[] alphas = null;
		int alphaLen = 0;
		if (pixelFormat.equals("DXT1")) {
			pixels = new int[4];
			alphas = new int[4];
		} else if (pixelFormat.equals("DXT3")) {
			pixels = new int[16];
			alphas = new int[16];
			alphaLen = 8;
		} else if (pixelFormat.equals("DXT5")) {
			pixels = new int[32];
			alphas = new int[32];
			alphaLen = 8;
		} else {
			throw new UnknowPixelFormatException("Unknow dds pixelFormat: " + pixelFormat);
		}

		BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);

		int numTilesWide = width / 4;
		int numTilesHigh = height / 4;
		for (int i = 0; i < numTilesHigh; i++) {
			for (int j = 0; j < numTilesWide; j++) {
				// Read the alpha table.
				long alphaData = 1;
				if (alphaLen == 0) {
				} else if (alphaLen == 4) {
					alphaData = buffer.getLong();
				} else if (alphaLen == 8) {
					alphaData = buffer.getLong();
				}
				for (int k = alphas.length - 1; k >= 0; k--) {
					if (alphaLen == 0) {
						alphas[k] = 240;
					} else {
						alphas[k] = (int) (alphaData >>> (k * 4)) & 0xF; // Alphas are just 4 bits per pixel
						alphas[k] <<= 4;
					}
				}

				short minColor = buffer.getShort();
				short maxColor = buffer.getShort();
				Color[] lookupTable = expandLookupTable(minColor, maxColor);

				int colorData = buffer.getInt();

				for (int k = pixels.length - 1; k >= 0; k--) {
					int colorCode = (colorData >>> k * 2) & 0x03;
					pixels[k] = (alphas[k] << 24) | getPixel888(multiplyAlpha(lookupTable[colorCode], alphas[k]));
				}

				result.setRGB(j * 4, i * 4, 4, 4, pixels, 0, 0);
			}
		}
		return result;
	}

	private static Color multiplyAlpha(Color color, int alpha) {
		Color result = new Color();

		double alphaF = alpha / 256.0;

		result.r = (int) (color.r * alphaF);
		result.g = (int) (color.g * alphaF);
		result.b = (int) (color.b * alphaF);
		return result;
	}

	protected static Color getColor565(int pixel) {
		Color color = new Color();

		color.r = (int) (((long) pixel) & 0xf800) >>> 8;
		color.g = (int) (((long) pixel) & 0x07e0) >>> 3;
		color.b = (int) (((long) pixel) & 0x001f) << 3;

		return color;
	}

	private static Color[] expandLookupTable(short minColor, short maxColor) {
		Color[] result = new Color[] { getColor565(minColor), getColor565(maxColor), new Color(), new Color() };

		result[2].r = (2 * result[0].r + result[1].r + 1) / 3;
		result[2].g = (2 * result[0].g + result[1].g + 1) / 3;
		result[2].b = (2 * result[0].b + result[1].b + 1) / 3;

		result[3].r = (result[0].r + 2 * result[1].r + 1) / 3;
		result[3].g = (result[0].g + 2 * result[1].g + 1) / 3;
		result[3].b = (result[0].b + 2 * result[1].b + 1) / 3;

		return result;
	}

	protected static int getPixel888(Color color) {
		int r = color.r;
		int g = color.g;
		int b = color.b;
		return r << 16 | g << 8 | b;
	}
}

class UnknowPixelFormatException extends Exception {
	public UnknowPixelFormatException() {
		super();
	}

	public UnknowPixelFormatException(String message) {
		super(message);
	}
}
